package udtp

import (
    "net"
    "errors"
    "fmt"
    "sync"
    "strconv"
)

var debug = true

type BufIn struct {
    udpAddr *net.UDPAddr
    buf []byte
}

type UDTP struct {
    conn *net.UDPConn
    isListening bool
    bufInChan chan BufIn
    clients map[string]*serverClient
    clientsLock sync.Mutex
}

func DebugPrint(a ...interface{}) {
    if debug {
        fmt.Println(a...)
    }
}

func New() (*UDTP) {
    udtp := new(UDTP)
    udtp.isListening = false
    udtp.bufInChan = make(chan BufIn, 16)
    udtp.clients = make(map[string]*serverClient)
    return udtp
}

func (u *UDTP) Listen(addr string) (retErr error) {
    if u.isListening {
        retErr = errors.New("already listening")
        return
    }
    udpAddr, err := net.ResolveUDPAddr("udp4", addr)
    if err != nil {
        retErr = err
        return
    }
    conn, err := net.ListenUDP("udp", udpAddr)
    if err != nil {
        retErr = err
        return
    }
    u.conn = conn
    u.isListening = true
    retErr = nil
    for i := 0; i < 3; i++ {
        go u.grtHandleBufIn()
    }
    go u.listenLoop()
    DebugPrint("running on", addr)
    return
}

func (u *UDTP) deleteClient(addr *net.UDPAddr) {
    key := addr.IP.String()+":"+strconv.Itoa(addr.Port)
    u.clientsLock.Lock()
    _, ok := u.clients[key]
    if ok {
        delete(u.clients, key)
    }
    u.clientsLock.Unlock()
}

func (u *UDTP) getClient(addr *net.UDPAddr) (sc *serverClient) {
    u.clientsLock.Lock()
    c, ok := u.clients[addr.IP.String()+":"+strconv.Itoa(addr.Port)]
    u.clientsLock.Unlock()
    if ok {
        sc = c
    } else {
        sc = nil
    }
    return
}

func (u *UDTP) addClient(addr *net.UDPAddr) (sc *serverClient) {
    u.clientsLock.Lock()
    _, ok := u.clients[addr.IP.String()+":"+strconv.Itoa(addr.Port)]
    if ok {
        sc = nil
    } else {
        sc = NewServerClient(u, u.conn, addr)
        u.clients[addr.IP.String()+":"+strconv.Itoa(addr.Port)] = sc
    }
    u.clientsLock.Unlock()
    return
}

func (u *UDTP) grtHandleBufIn() {
    for {
        bufIn := <-u.bufInChan
        if len(bufIn.buf) < 8 {
            DebugPrint("receive a tiny packet")
            continue
        }
        if sc := u.getClient(bufIn.udpAddr); sc != nil {
            sc.bufIn <- bufIn.buf
        } else {
            if sc := u.addClient(bufIn.udpAddr); sc != nil {
                sc.bufIn <- bufIn.buf
            }
        }
    }
}

func (u *UDTP) listenLoop() {
    for {
        buf := new([1200]byte)
        n, rAddr, err := u.conn.ReadFromUDP(buf[0:])
        if err != nil {
            DebugPrint(err)
        }
        if n > 0 {
            u.bufInChan <- BufIn{udpAddr:rAddr, buf:buf[0:n]}
        }
    }
}